package hasthi.metamodel.resource;

import static hasthi.common.HasthiOptions.SYSTEM_PORFILE;
import hasthi.actuators.SystemConfig;
import hasthi.common.ChangeListener;
import hasthi.common.HasthiError;
import hasthi.common.HasthiException;
import hasthi.common.InvaliedResourceDataException;
import hasthi.common.Utils;
import hasthi.common.HasthiException.FaultCode;
import hasthi.common.constants.HasthiConstants;
import hasthi.config.ResourceProfile;
import hasthi.config.SystemProfileDocument;
import hasthi.container.MessageContext;
import hasthi.container.SimpleMessageContext;
import hasthi.container.UnicastContianier;
import hasthi.manager.GetResourcesDocument;
import hasthi.manager.GetResourcesResponseDocument;
import hasthi.manager.ListAllResourcesDocument;
import hasthi.manager.ListAllResourcesResponseDocument;
import hasthi.manager.ManagedResourceType;
import hasthi.manager.ManagerClientUtils;
import hasthi.metamodel.ChangeBuffer;
import hasthi.metamodel.ResourceModel;
import hasthi.metamodel.resource.ManagedResource.ResourceType;
import hasthi.tools.ManagementRingTools;

import java.io.File;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import javax.xml.namespace.QName;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.xmlbeans.XmlException;

public abstract class AbstractResourceModel implements ResourceModel {
    private static Log log = LogFactory.getLog(HasthiConstants.LOGGER_NAME);

    protected ResourceModelContext context;
    
    private Map<String,String> resourceImplMap = new ConcurrentHashMap<String, String>();

    public static enum Placement {
        Remote, Local, Summerized
    };

    /**
     * @param coordinatorURL
     *                if this is null, coordinator is assumed
     * @throws HasthiException 
     */
    public AbstractResourceModel(ResourceModelContext context) throws HasthiException {
        try {
            this.context = context;
            
            if(context.getBaseContext() != null){
                String systemProfileFile = context.getBaseContext().getProperty(SYSTEM_PORFILE);
                if(systemProfileFile != null){
                    SystemProfileDocument profileDocument = SystemProfileDocument.Factory.parse(new File(systemProfileFile));
                    profileDocument.validate();
                    
                    
                    ResourceProfile[] resourceProfiles = profileDocument.getSystemProfile().getResourceArray();
                    
                    for(ResourceProfile profile:resourceProfiles){
                        String implClass = profile.getImplClass();
                        if(implClass != null){
                            //this makes sure system will fail at the startup if there is a wrong value
                            //here
                            Utils.loadClass(implClass).newInstance();
                            resourceImplMap.put(profile.getName().toString(), implClass);
                        }
                    }
                }
            }
        } catch (XmlException e) {
            throw new HasthiException(e,FaultCode.InitalizationError);
        } catch (IOException e) {
            throw new HasthiException(e,FaultCode.InitalizationError);
        } catch (InstantiationException e) {
            throw new HasthiException(e,FaultCode.InitalizationError);
        } catch (IllegalAccessException e) {
            throw new HasthiException(e,FaultCode.InitalizationError);
        } catch (ClassNotFoundException e) {
            throw new HasthiException(e,FaultCode.InitalizationError);
        }
    }

    protected EditableManagedResource createNewResource(Placement placement,
            Map<QName, Object> extractedProperties, String managerAgentURL)
            throws InvaliedResourceDataException, HasthiException {
        EditableManagedResource resource;
        switch (placement) {
        case Local:
            resource = new ManagedLocalResourceImpl(context, extractedProperties,
                    managerAgentURL);
            break;
        case Remote:
            resource = new ManagedRemoteResourceImpl(context, extractedProperties, managerAgentURL);
            break;
        default:// case Summerized:
            resource = new SummerizedManagedResource(context, extractedProperties, managerAgentURL);
        }

        EditableManagedResource wrapperdResource = wrapResource(resource);
        return wrapperdResource;
    }
    
    protected EditableManagedResource createNewResource(Placement placement,
            QName resourceName,String managerAgentURL, String category, String userDefinedType)
            throws InvaliedResourceDataException, HasthiException {
        EditableManagedResource resource;
        switch (placement) {
        case Local:
            resource = new ManagedLocalResourceImpl(resourceName,context, managerAgentURL,category, userDefinedType);
            break;
        case Remote:
            resource = new ManagedRemoteResourceImpl(resourceName,context, managerAgentURL,category, userDefinedType);
            break;
        default:// case Summerized:
            resource = new SummerizedManagedResource(resourceName,context, managerAgentURL,category, userDefinedType);
        }

        EditableManagedResource wrapperdResource = wrapResource(resource);
        return wrapperdResource;
    }
    
    
   
    
    protected EditableManagedResource createNewResource(Placement placement,ManagedResourceType resourceData) 
        throws InvaliedResourceDataException, HasthiException{
        EditableManagedResource resource;
        switch (placement) {
        case Local:
            resource = new ManagedLocalResourceImpl(context, resourceData);
            break;
        case Remote:
            resource = new ManagedRemoteResourceImpl(context, resourceData);
            break;
        default:// case Summerized:
            resource = new SummerizedManagedResource(context, resourceData);
        }

        EditableManagedResource wrapperdResource = wrapResource(resource);
        return wrapperdResource;
    }

    private EditableManagedResource wrapResource(EditableManagedResource resource) throws HasthiException {
        try {
            EditableManagedResource wrapperdResource;
            ResourceType categoery = ResourceType.valueOf(resource.getCategory());
            
            switch (categoery) {
                case Service:case TransientService:
                    String implementationClass = resourceImplMap.get(resource.getType());
                    if(implementationClass != null){
                        Class type = Utils.loadClass(implementationClass);
                        AbstractWrapperResource tempResource = ((AbstractWrapperResource)type.newInstance());
                        tempResource.init(resource);
                        wrapperdResource = tempResource;
                    }else{
                        wrapperdResource = new ManagedService(resource);    
                    }
                    break;
                default:// case Host:
                    wrapperdResource = new Host(resource);
                    break;
            }
            ChangeListener modelListener = context.getChangeListener();
            if (modelListener != null) {
                modelListener.resourceAdded(wrapperdResource.getQName());
            }
            return wrapperdResource;
        } catch (ClassNotFoundException e) {
            throw new HasthiException(e,FaultCode.InitalizationError);
        } catch (InstantiationException e) {
            throw new HasthiException(e,FaultCode.InitalizationError);
        } catch (IllegalAccessException e) {
            throw new HasthiException(e,FaultCode.InitalizationError);
        }
    }

    /**
     * Get all resources
     * 
     * @return
     * @throws HasthiException
     */
    public ManagedResource[] getAllResources() throws HasthiException {
        // TODO may be we need to support finding resource based on properties
        // rather than listing all

        ListAllResourcesDocument listAllResourcesDocument = ListAllResourcesDocument.Factory
                .newInstance();
        listAllResourcesDocument.addNewListAllResources();

        UnicastContianier container = context.getContainer();
        MessageContext requestMessage = new SimpleMessageContext(listAllResourcesDocument, context
                .getCoordinatorUrl(), HasthiConstants.GET_RESOURCES_HOLDER_ACTION);
        ListAllResourcesResponseDocument responseDocument = (ListAllResourcesResponseDocument) container
                .sendReceive(requestMessage);

        ManagedResourceType[] resourcesAsXbeans = responseDocument.getListAllResourcesResponse()
                .getResourceDataArray();

        int i = 0;
        ManagedResource[] resources;
        try {
            resources = new ManagedResource[resourcesAsXbeans.length];
            for (ManagedResourceType resourceAsXbean : resourcesAsXbeans) {
                resources[i] = new SummerizedManagedResource(context, resourceAsXbean);
                if(ManagedResource.ResourceType.Host.toString().equals(resources[i].getCategory())){
                    resources[i] = new Host((EditableManagedResource)resources[i]);
                }else if(ManagedResource.ResourceType.Service.toString().equals(resources[i].getCategory())){
                    resources[i] = new ManagedService((EditableManagedResource)resources[i]);
                }
                i++;
            }
        } catch (InvaliedResourceDataException e) {
            // We do not expect this to happen, if it does we have a Bug at our
            // hands
            throw new HasthiError("PossibleErrorAtServer", e);
        }
        return resources;
    }

    public ChangeBuffer getChangeBuffer() {
        ChangeListener modelListener = context.getChangeListener();
        if(modelListener != null && modelListener.getClass().equals(ChangeBuffer.class)){
            return (ChangeBuffer)modelListener;    
        }
        return null;
    }

    /**
     * tries to reload the coordinator
     * 
     * @throws HasthiException
     */
    public void reload() throws HasthiException, InterruptedException {
        /**
         * We will reload if there was a coordination context exists
         */
        // System.out.println(context.getCoordinatorUrl());
        if (context.getCoordinatorUrl() != null) {
            String coordinatorUrl = ManagementRingTools.findCoordinatorFromBootstrapNode(context
                    .getContainer(), context.getBaseContext());
            context.setCoordinatorUrl(coordinatorUrl);
        }
    }

    protected ManagedResource fetchRemoteResource(String holderUrl, QName resourceID,
            boolean summerized) throws HasthiException {
        UnicastContianier container = context.getContainer();

        GetResourcesDocument resourcesDocument = ManagerClientUtils.createGetResourcesRequest(
                new QName[] { resourceID }, false);
        MessageContext msgContext = new SimpleMessageContext(resourcesDocument, holderUrl,
                HasthiConstants.GET_RESOURCES_ACTION);
        GetResourcesResponseDocument responseDocument = (GetResourcesResponseDocument) container
                .sendReceive(msgContext);
        ManagedResourceType[] resources = responseDocument.getGetResourcesResponse()
                .getResourceArray();

        if (resources.length > 0) {
            try {
                return createNewResource(Placement.Remote,resources[0]);
            } catch (InvaliedResourceDataException e) {
                // This should never happen
                throw new HasthiError("PossibleBugAtServer", e);
            }
        } else {
            log.warn("No remote resource found under " + holderUrl + " named  " + resourceID);
            return null;
        }
    }

    public ResourceModelContext getResourceModelContext() {
        return context;
    }
}
